package study;

import com.google.common.collect.Lists;
import com.google.inject.*;
import com.google.inject.Module;
import org.jboss.resteasy.plugins.guice.ModuleProcessor;
import org.jboss.resteasy.plugins.guice.ext.JaxrsModule;
import org.jboss.resteasy.spi.ResteasyDeployment;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.scanners.TypeAnnotationsScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import javax.ws.rs.Path;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Set;

/**
 * Created by 徐翔 on 14-3-11.
 */
public class Jedi {
    public static final Jedi instance = new Jedi();
    private Logger logger = LoggerFactory.getLogger("Fly");
    private Properties confProps;
    protected ResteasyDeployment deployment;
    private Injector injector;
    private List<Module> modules;

    public void initialized(final ResteasyDeployment rd){
        // load conf.properties
//        loadProperties();
        rd.start();
        modules = Lists.newArrayList();
//        modules.add(new JaxrsModule());
        modules.addAll(getModules());
        modules.add(new GuiceModule());
        modules.add(new AbstractModule() {
            @Override
            protected void configure() {
                for (String provider : rd.getScannedProviderClasses()){
                    try {
                        bind(Class.forName(provider));
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        final Stage stage = getStage();
        if (stage == null)
        {
            injector = Guice.createInjector(modules);
        }
        else
        {
            injector = Guice.createInjector(stage, modules);
        }

        ModuleProcessor processor = new ModuleProcessor(rd.getRegistry(),rd.getProviderFactory());
        processor.processInjector(injector);

        while (injector.getParent() != null) {
            injector = injector.getParent();
            processor.processInjector(injector);
        }
        triggerAnnotatedMethods(PostConstruct.class);
    }




    protected List<Module> getModules()
    {
        final List<Module> result = new ArrayList<Module>();
        final String modulesString = confProps.getProperty("resteasy.guice.modules");
        if (modulesString != null)
        {
            final String[] moduleStrings = modulesString.trim().split(",");
            for (final String moduleString : moduleStrings)
            {
                try
                {
                    logger.info("found module: {0}", moduleString);
                    final Class clazz = Thread.currentThread().getContextClassLoader().loadClass(moduleString.trim());
                    final Module module = (Module) clazz.newInstance();
                    result.add(module);
                }
                catch (ClassNotFoundException e)
                {
                    throw new RuntimeException(e);
                }
                catch (IllegalAccessException e)
                {
                    throw new RuntimeException(e);
                }
                catch (InstantiationException e)
                {
                    throw new RuntimeException(e);
                }
            }

        }
        return result;
    }

    protected Stage getStage()
    {
        final String stageAsString = confProps.getProperty("resteasy.guice.stage");
        if (stageAsString == null)
        {
            return null;
        }
        try
        {
            return Stage.valueOf(stageAsString.trim());
        }
        catch (IllegalArgumentException e)
        {
            throw new RuntimeException("Injector stage is not defined properly. " + stageAsString + " is wrong value." +
                    " Possible values are PRODUCTION, DEVELOPMENT, TOOL.");
        }
    }
    public void loadProperties(){
        confProps = new Properties();
        InputStream in = null;
        try {
            in = Jedi.class.getResourceAsStream("/conf.properties");
            confProps.load(in);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
                System.exit(1);
            }
        }
    }

    private void triggerAnnotatedMethods(final Class<? extends Annotation> annotationClass)
    {
        for (Module module : this.modules)
        {
            final Method[] methods = module.getClass().getMethods();
            for (Method method : methods)
            {
                if (method.isAnnotationPresent(annotationClass))
                {
                    if(method.getParameterTypes().length > 0)
                    {
                        logger.warn("Cannot execute expected module {}'s @{} method {} because it has unexpected parameters: skipping.", module.getClass().getSimpleName(), annotationClass.getSimpleName(), method.getName());
                        continue;
                    }
                    try
                    {
                        method.invoke(module);
                    } catch (InvocationTargetException ex) {
                        logger.warn("Problem running annotation method @" + annotationClass.getSimpleName(), ex);
                    } catch (IllegalAccessException ex) {
                        logger.warn("Problem running annotation method @" + annotationClass.getSimpleName(), ex);
                    }
                }
            }
        }
    }


    public void destroyed(){
        deployment.stop();
    }

    public Set<Class<?>> getPathClasses(){
        Reflections reflections = new Reflections(new ConfigurationBuilder()
                .filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix("study")))
                .setUrls(ClasspathHelper.forPackage("study"))
                .setScanners(new TypeAnnotationsScanner(), new SubTypesScanner()));
        return reflections.getTypesAnnotatedWith(Path.class);
    }
    public Injector getInjector(){
        return this.injector;
    }
    public Properties getConfProps(){
       return this.confProps;
    }

    public ResteasyDeployment getDeployment(){
        return deployment;
    }
}
